package com.foo.common.base.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import com.foo.common.base.entity.FooUserTrip;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import com.foo.common.base.pojo.ClassFieldForMustache;
import com.foo.common.base.pojo.ClassFieldGetAndSetForMustache;
import com.foo.common.base.pojo.OracleUserTabColumnsModel;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;

/**
 * 警告：：：目前对于mysql的主键生成策略是默认单主键，并且名称一定为id。复合主键的情况有需要再进行完善
 *
 * @author Steve
 */
public enum DatabaseTablesHelper {

    INSTANCE;

    // private static String myDialect = "mysql";
    private static String myDialect = "oracle";

    private static Logger logger = LoggerFactory
            .getLogger(DatabaseTablesHelper.class);

    private static final String tableName = "ITMS_ORDER_ERRORCODE";
    private static final String hibernate_oracle_properties = "hibernate-localhost-oracle.properties";
    private static final String hibernate_mysql_properties = "hibernate-localhost-mysql.properties";

    private String generatedJavaFileDirectory = FooUtils.global_test_file_working_directory_str
            + "\\autoGeneratedModel";

    public static void main(String[] args) throws Exception {
        if (myDialect.equals("mysql")) {
            DatabaseTablesHelper.INSTANCE
                    .generateWithMustache(INSTANCE.getMysqlSqlResult());
        } else {
            DatabaseTablesHelper.INSTANCE.getOracleSqlResult();
        }
    }

    @Test
    public void createTableOnEntity() throws IOException {
        ClassPathResource myPath = new ClassPathResource("hibernate-localhost-mysql.properties");
        Assert.assertTrue(myPath.exists());

        Properties p = new Properties();
        p.load(myPath.getInputStream());

        Configuration configuration = new Configuration().setProperties(p);

        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();

        SessionFactory myFactory = configuration.addAnnotatedClass(FooUserTrip.class).buildSessionFactory(serviceRegistry);

        Session session = myFactory.openSession();

        Transaction tx = null;

        // See:http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch13.html#transactions-demarcation-nonmanaged
        try {
            tx = session.beginTransaction();
            // tx.setTimeout(200);
            List<FooUserTrip> fooUserTrips = Lists.newArrayList(session.createQuery("from FooUserTrip").list());
            logger.info("fooUserTrips size is:{}", fooUserTrips.size());
            tx.commit();
        } catch (RuntimeException e) {
            if (tx != null)
                tx.rollback();
            throw e;
        } finally {
            session.close();
            myFactory.close();
        }
    }


    private void generateWithMustache(List<Object[]> fieldsList)
            throws Exception {
        MustacheFactory mf = new DefaultMustacheFactory();

        String myPackage = "com.feiynn.tenmalife.model";
        String myClassName = CaseFormat.UPPER_UNDERSCORE
                .to(CaseFormat.UPPER_CAMEL, tableName.toUpperCase());

        boolean hasDateField = false;
        String myFieldName;
        String myFieldNameFirstUpper;
        String myFieldType;
        String myColumnName;
        boolean myNullable;
        String myFieldTypeInitExp;
        String myLengthExp;

        HashMap<String, Object> scopes = new HashMap<String, Object>();
        scopes.put("myPackage", myPackage);
        scopes.put("myTableName", tableName.toUpperCase());
        scopes.put("myClassName", myClassName);

        List<ClassFieldForMustache> fields = Lists.newArrayList();
        ClassFieldForMustache filed;

        List<ClassFieldGetAndSetForMustache> fieldsGetAndSet = Lists
                .newArrayList();
        ClassFieldGetAndSetForMustache fieldGetAndSetObject;

        for (Object[] result : fieldsList) {
            myColumnName = result[0].toString();
            myFieldName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,
                    myColumnName.toUpperCase());

            // TODOO 这里默认id为主键，所以不进行id的解析了
            if (myFieldName.toLowerCase().equals("id")) {
                continue;
            }

            myFieldNameFirstUpper = StringUtils.capitalize(myFieldName);

            myFieldType = getMyFieldTypeForMysql(result[1].toString());
            if (myFieldType.equals("Date")) {
                hasDateField = true;
            }

            myFieldTypeInitExp = getMyFieldTypeInitExpForMysql(
                    result[1].toString());
            myLengthExp = getMyLengthExpForMysql(result[1].toString());

            myNullable = result[2].toString().toLowerCase().equals("yes") ? true
                    : false;

            filed = new ClassFieldForMustache();
            filed.setMyColumnName(myColumnName);
            filed.setMyFieldName(myFieldName);
            filed.setMyFieldType(myFieldType);
            filed.setMyFieldTypeInitExp(myFieldTypeInitExp);
            filed.setMyLengthExp(myLengthExp);
            filed.setMyNullable(myNullable);
            fields.add(filed);

            fieldGetAndSetObject = new ClassFieldGetAndSetForMustache();
            fieldGetAndSetObject.setMyFieldName(myFieldName);
            fieldGetAndSetObject
                    .setMyFieldNameFirstUpper(myFieldNameFirstUpper);
            fieldGetAndSetObject.setMyFieldType(myFieldType);
            fieldsGetAndSet.add(fieldGetAndSetObject);

        }

        // fieldGetAndSetObject = new ClassFieldGetAndSetForMustache();
        // fieldGetAndSetObject.setMyFieldName("id");
        // fieldGetAndSetObject.setMyFieldNameFirstUpper("Id");
        // fieldGetAndSetObject.setMyFieldType("String");
        // fieldsGetAndSet.add(fieldGetAndSetObject);

        scopes.put("hasDateField", hasDateField);
        scopes.put("fields", fields);
        scopes.put("fieldsGetAndSet", fieldsGetAndSet);

        Mustache mustache = mf.compile("template-generic-mysql-model.mustache");

        Writer writer = new StringWriter();

        mustache.execute(writer, scopes);
        writer.flush();

        String result = writer.toString();
        logger.info("result is: \n\n\n {}", result);

        FooUtils.forceMkdir(generatedJavaFileDirectory);
        File resultFile = new File(
                generatedJavaFileDirectory + "\\" + myClassName + ".java");

        IOUtils.write(result, new FileOutputStream(resultFile), "utf-8");
        logger.info("generate file:{} on path:{}", resultFile.getName(),
                generatedJavaFileDirectory);
    }

    /**
     * token passed in will be the following formats:
     * <p>
     * varchar(255),int(11),datetime
     *
     * @param token
     * @return
     */
    private String getMyFieldTypeInitExpForMysql(String token) {
        String type = getMyFieldTypeForMysql(token);
        if (type.equals("String")) {
            return " \"\" ";
        } else if (type.equals("int")) {
            return " 0 ";
        } else if (type.equals("Date")) {
            return " new Date() ";
        } else if (type.equals("double")) {
            return " 0 ";
        } else {
            throw new UnsupportedOperationException(token);
        }
    }

    /**
     * token passed in will be the following formats:
     * <p>
     * varchar(255),int(11),datetime
     *
     * @param token
     * @return
     */
    private String getMyLengthExpForMysql(String token) {
        String type = getMyFieldTypeForMysql(token);
        if (type.equals("String")) {
            return ", length = " + FooUtils.extractIntegerFromString(token)
                    + " ";
        }
        return "";
    }

    /**
     * token passed in will be the following formats:
     * <p>
     * varchar(255),int(11),datetime
     *
     * @param token
     * @return
     */
    private String getMyFieldTypeForMysql(String token) {
        if (token.contains("varchar")) {
            return "String";
        } else if (token.contains("int")) {
            return "int";
        } else if (token.contains("datetime")) {
            return "Date";
        } else if (token.contains("decimal")) {
            return "double";
        } else if (token.contains("date")) {
            return "Date";
        } else {
            throw new UnsupportedOperationException(token);
        }
    }

    private List<Object[]> getMysqlSqlResult() throws Exception {
        ClassPathResource myPath = new ClassPathResource(
                hibernate_mysql_properties);

        Assert.assertTrue(myPath.exists());

        Properties p = new Properties();
        p.load(myPath.getInputStream());

        Configuration configuration = new Configuration().setProperties(p);

        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                .applySettings(configuration.getProperties()).build();

        SessionFactory myFactory = configuration
                .addAnnotatedClass(OracleUserTabColumnsModel.class)
                .buildSessionFactory(serviceRegistry);

        Session session = myFactory.openSession();

        Transaction tx = null;

        tx = session.beginTransaction();
        @SuppressWarnings("unchecked")
        List<Object[]> myList = (List<Object[]>) session
                .createSQLQuery(" desc " + tableName + "").list();
        tx.commit();
        session.close();
        myFactory.close();

        return myList;

    }

    /**
     * 生成pojo的filed描述语句，一般情况下已经够用
     *
     * @param session
     * @throws IOException
     */
    void doInTransaction(Session session) throws IOException {

        // String tableName = "STB_UPGRADE_STRATEGY";

        // 是否需要添加注解。如果不需要getObject操作的话，可以禁用这项。
        boolean withAnnotation = true;

        @SuppressWarnings("unchecked")
        List<OracleUserTabColumnsModel> myList = session.createQuery(
                " FROM OracleUserTabColumnsModel WHERE table_name='" + tableName
                        + "' ")
                .list();

        String myDataType = "";
        String oriColumnName = "";
        String javaCamelColumnName = "";

        StringBuilder sb = new StringBuilder();

        sb.append("@@Entity");
        sb.append("@Table(name = \"" + tableName + "\")");
        sb.append("\n");
        sb.append(FooUtils.getSeparateLine());
        sb.append("\n");

        for (OracleUserTabColumnsModel oracleUserTabColumnsModel : myList) {
            myDataType = oracleUserTabColumnsModel.getData_type();
            oriColumnName = oracleUserTabColumnsModel.getPk().getColumn_name()
                    .toUpperCase();
            javaCamelColumnName = CaseFormat.UPPER_UNDERSCORE
                    .to(CaseFormat.LOWER_CAMEL, oriColumnName);

            if (withAnnotation) {
                sb.append("@Column(name = \"" + oriColumnName + "\")");
                sb.append("\n");
            }

            sb.append("private");

            if (myDataType.contains("date") || myDataType.contains("time")) {

                sb.append(" Date " + javaCamelColumnName + " = new Date();");
            } else if (myDataType.contains("number")) {
                sb.append(" int " + javaCamelColumnName + " = 0;");
            } else if (myDataType.contains("double")) {
                sb.append(" double " + javaCamelColumnName + " = 0;");
            } else if (myDataType.contains("varchar")) {
                sb.append(" String " + javaCamelColumnName + " = \"\";");
            } else {
                sb.append(" String " + javaCamelColumnName + " = \"\";");
            }
            System.out.println(sb.toString());
            sb = new StringBuilder();
        }
        logger.info("generate end.{}", FooUtils.getSeparateLine());
    }

    public SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                    .build();
            return new Configuration().configure()
                    .buildSessionFactory(serviceRegistry);
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    private void getOracleSqlResult() throws IOException {

        ClassPathResource myPath = new ClassPathResource(
                hibernate_oracle_properties);

        Assert.assertTrue(myPath.exists());

        Properties p = new Properties();
        p.load(myPath.getInputStream());

        Configuration configuration = new Configuration().setProperties(p);

        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                .applySettings(configuration.getProperties()).build();

        SessionFactory myFactory = configuration
                .addAnnotatedClass(OracleUserTabColumnsModel.class)
                .buildSessionFactory(serviceRegistry);

        Session session = myFactory.openSession();

        Transaction tx = null;

        // See:http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch13.html#transactions-demarcation-nonmanaged
        try {
            tx = session.beginTransaction();
            // tx.setTimeout(200);
            doInTransaction(session);
            tx.commit();
        } catch (RuntimeException e) {
            if (tx != null)
                tx.rollback();
            throw e;
        } finally {
            session.close();
            myFactory.close();
        }
    }

}
